Описание проекта¶

Банки — Анализ оттока клиентов Необходимо проанализировать клиентов регионального банка «Метанпром» и выделить сегменты клиентов, которые склонны уходить из банка.

  • Провести исследовательский анализ данных,
  • Выделить портреты клиентов, которые склонны уходить из банка,
  • Сформулировать и проверите статистические гипотезы.

Описание датасетов¶

Датасет содержит данные о клиентах банка «Метанпром». Банк располагается в Ярославле и областных городах: Ростов Великий и Рыбинск.

Колонки:

  • userid — идентификатор пользователя,
  • score — баллы кредитного скоринга,
  • City — город,
  • Gender — пол,
  • Age — возраст,
  • Equity — приблизительная оценка собственности клиента,
  • Balance — баланс на счёте,
  • Products — количество продуктов, которыми пользуется клиент,
  • CreditCard — есть ли кредитная карта,
  • last_activity — активность клиента 1(активный)/0(неактивный),
  • est_salary — заработная плата клиента,
  • Churn — ушёл или нет.

Содержание¶

  1. Открытие данных и общая информация
  1. Предобработка данных

    2.1 Приведение заголовков столбцов к "змеиному" регистру

    2.2 Изменение типов данных

    2.3 Обработка пропущенных значений

    2.4 Обработка дубликатов

    2.5 Обработка аномальных значений

    2.6 Создание столбца с категорией города (цифра соответствует определённому городу)

  1. Анализ данных

    3.1 Определить зависимость баллов кредитного скоринга и вероятности ухода

    3.2 Определить зависимость вероятности ухода от города

    3.3 Какого пола клиенты чаще уходят

    3.4 Определить зависимость вероятности ухода от возраста

    3.5 Определить зависимость вероятности ухода от оценки собственности клиента

    3.6 Определить зависимость вероятности ухода от баланса на счёте

    3.7 Определить зависимость вероятности ухода от количества продуктов, которыми пользуется клиент

    3.8 Определить зависимость вероятности ухода от наличия кредитной карты

    3.9 Определить зависимость вероятности ухода от активности

    3.10 Определить зависимость вероятности ухода от заработной платы

    3.11 Произвести анализ вероятности ухода в зависимости сразу от нескольких критериев

    3.12 Составить портрет клиента, склонного уходить

    3.13 Выводы

  1. Проверка 3-х гипотез

    4.1 Формулирование гипотез

    4.2 Тестирование первой гипотезы

    4.3 Тестирование второй гипотезы

    4.4 Тестирование третьей гипотезы

    4.5 Выводы

  1. Презентация

Открытие данных и оющая информация¶

In [1]:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from scipy import stats as st
import plotly.figure_factory as ff
import plotly.express as px
from plotly import graph_objects as go
from plotly.subplots import make_subplots
In [2]:
path = "https://drive.google.com/uc?export=download&id=1-U61mhTz_N1ARjy2XSAZ7IlQqGjeqP0F"
df = pd.read_csv(path)
df.head(5)
Out[2]:
USERID score city gender age equity balance products credit_card last_activity EST_SALARY churn
0 183012 850.0 Рыбинск Ж 25 1 59214.82 2 0 1 75719.14 1
1 146556 861.0 Рыбинск Ж 37 5 850594.33 3 1 0 86621.77 0
2 120722 892.0 Рыбинск Ж 30 0 NaN 1 1 1 107683.34 0
3 225363 866.0 Ярославль Ж 51 5 1524746.26 2 0 1 174423.53 1
4 157978 730.0 Ярославль М 34 5 174.00 1 1 0 67353.16 1
In [3]:
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 10000 entries, 0 to 9999
Data columns (total 12 columns):
 #   Column         Non-Null Count  Dtype  
---  ------         --------------  -----  
 0   USERID         10000 non-null  int64  
 1   score          10000 non-null  float64
 2   city           10000 non-null  object 
 3   gender         10000 non-null  object 
 4   age            10000 non-null  int64  
 5   equity         10000 non-null  int64  
 6   balance        7705 non-null   float64
 7   products       10000 non-null  int64  
 8   credit_card    10000 non-null  int64  
 9   last_activity  10000 non-null  int64  
 10  EST_SALARY     10000 non-null  float64
 11  churn          10000 non-null  int64  
dtypes: float64(3), int64(7), object(2)
memory usage: 937.6+ KB
In [4]:
df.describe()
Out[4]:
USERID score age equity balance products credit_card last_activity EST_SALARY churn
count 10000.00000 10000.000000 10000.000000 10000.000000 7.705000e+03 10000.000000 10000.000000 10000.000000 1.000000e+04 10000.000000
mean 171814.71260 848.699400 42.837100 2.627600 8.277943e+05 1.874100 0.680400 0.523500 1.478669e+05 0.182200
std 33708.23812 65.448519 12.128507 1.980836 1.980614e+06 0.799946 0.466345 0.499472 1.393885e+05 0.386029
min 94561.00000 642.000000 18.000000 0.000000 0.000000e+00 0.000000 0.000000 0.000000 2.546300e+03 0.000000
25% 142810.25000 802.000000 34.000000 0.000000 2.955542e+05 1.000000 0.000000 0.000000 7.525190e+04 0.000000
50% 172728.00000 853.000000 40.000000 3.000000 5.242722e+05 2.000000 1.000000 1.000000 1.196581e+05 0.000000
75% 201261.75000 900.000000 51.000000 4.000000 9.807058e+05 2.000000 1.000000 1.000000 1.745005e+05 0.000000
max 229145.00000 1000.000000 86.000000 9.000000 1.191136e+08 5.000000 1.000000 1.000000 1.395064e+06 1.000000
In [5]:
df.hist(figsize=(15,20));

В датасете 10000 строк и 12 столбцов. Только в столбце balance есть пропуски. Каких-то аномальных данных из "описания" пока не видно.

Предобработка данных¶

Приведение заголовков столбцов к "змеиному" регистру¶

In [6]:
df.columns = df.columns.str.lower()
df.columns
Out[6]:
Index(['userid', 'score', 'city', 'gender', 'age', 'equity', 'balance',
       'products', 'credit_card', 'last_activity', 'est_salary', 'churn'],
      dtype='object')

Изменение типов данных¶

Имеет смысл у столбца score изменить тип данных с float64 на uint16 (целые числа в диапазоне от 0 по 65535) т.к. это целые значения от 0 до 1000.

Также у столбцов age, equity, products, credit_card, last_activity, churn изменить тип с int64 на uint8 (целые числа в диапазоне от 0 по 255) для ускорения расчётов и экономии оперативной памяти.

In [7]:
df[['age','equity', 'products', 'credit_card', 'last_activity', 'churn']] = \
df[['age','equity', 'products', 'credit_card', 'last_activity', 'churn']] .astype('uint8')
df['score'] = df['score'].astype('uint16')

Обработка пропущенных значений¶

In [8]:
#проверим данные на наличие пропусков
print(df.isna().sum())
userid              0
score               0
city                0
gender              0
age                 0
equity              0
balance          2295
products            0
credit_card         0
last_activity       0
est_salary          0
churn               0
dtype: int64
In [9]:
fig = px.histogram(df, x="balance", color="churn", marginal="rug",
                   hover_data=df.columns, title='Распределение данных столбца balance')
fig.update_layout(xaxis_title='Баланс счёта клиента')
fig.show()

Распределение данных скошено вправо в сторону высоких значений баланса, поэтому заполнять пропуски средним значением будет ошибкой. Заполним пропуски медианным значением балланса, медиану посчитаем у уходящих и остающихся клиентов отдельно, предполагая, что есть разница в балансе, что подтверждает график выше.

Обработка дубликатов¶

Проверим уникальные названия городов, если есть повторяющиеся с разными регистрами, приведём их к одному

In [10]:
df['city'].unique()
Out[10]:
array(['Рыбинск', 'Ярославль', 'Ростов'], dtype=object)

С городами всё в порядке. Проверим тоже самое с в столбуе gender

In [11]:
df['gender'].unique()
Out[11]:
array(['Ж', 'М'], dtype=object)

Тоже всё в порядке

In [12]:
#проверим количесво дубликатов
df.duplicated().sum()
Out[12]:
1
In [13]:
# удалим дубликат и убедимся, что удалили его
df = df[df.duplicated()!=True]
df.duplicated().sum()
Out[13]:
0

Обработка аномальных значений¶

In [14]:
df.describe()
Out[14]:
userid score age equity balance products credit_card last_activity est_salary churn
count 9999.000000 9999.000000 9999.000000 9999.000000 7.705000e+03 9999.000000 9999.000000 9999.000000 9.999000e+03 9999.000000
mean 171817.699870 848.691369 42.838084 2.627863 8.277943e+05 1.874187 0.680368 0.523552 1.478435e+05 0.182218
std 33708.600055 65.446864 12.128714 1.980761 1.980614e+06 0.799938 0.466357 0.499470 1.393758e+05 0.386044
min 94561.000000 642.000000 18.000000 0.000000 0.000000e+00 0.000000 0.000000 0.000000 2.546300e+03 0.000000
25% 142815.500000 802.000000 34.000000 0.000000 2.955542e+05 1.000000 0.000000 0.000000 7.525178e+04 0.000000
50% 172740.000000 853.000000 40.000000 3.000000 5.242722e+05 2.000000 1.000000 1.000000 1.196547e+05 0.000000
75% 201262.500000 900.000000 51.000000 4.000000 9.807058e+05 2.000000 1.000000 1.000000 1.744997e+05 0.000000
max 229145.000000 1000.000000 86.000000 9.000000 1.191136e+08 5.000000 1.000000 1.000000 1.395064e+06 1.000000

Аномальные значения могут встретиться в столбцах age, balance и est_salary. Построим по ним диаграммы размаха.

In [15]:
plt.title("Диаграмма размаха  данных столбца 'age'")  
ax=sns.boxplot(x = df['age'])
ax.set(xlabel='Возраст клиентов')  
plt.show()

Старше 77 лет уже не совсем типичные клиенты банка.

In [16]:
plt.title("Диаграмма размаха  данных столбца 'balance'")  
ax=sns.boxplot(x = df['balance'])
ax.set(xlabel='Баланс счёта') 
plt.show()

Данные сильно смещены, очень много выбросов, которые могут искажать картину. Чтобы немного выровнять имеет смысл удалить пользователей с балансом выше 20 млн.

In [17]:
plt.title("Диаграмма размаха  данных столбца 'est_salary'")  
ax=sns.boxplot(x = df['est_salary'])
ax.set(xlabel='Предполагаемый ежемесячный доход') 
plt.show()

Данные тоже смещены. Все данные "за усами" исключить нельзя, т.к. мы лишаемся большого количества данных. Попробуем взять границу в 600 тыс.

Посчитаем какое количество данных мы удалим, применяя критерии выше.

In [18]:
print(round(len(df[(df['balance'] > 20000000) | (df['est_salary'] > 600000) | (df['age'] > 77)])/len(df['userid'])*100,2), '%')
2.62 %

2.62% это приемлемое количество данных для удаления (<5). Удаляем

In [19]:
index= df[(df['balance'] > 20000000) | (df['est_salary']> 600000) |(df['age'] > 77)].index.to_list()
In [20]:
df1 = df.drop(index = index)
len(df1)
Out[20]:
9737
In [21]:
df =df1.copy()

Создание дополнительных столбцов с категорией города в котором находится банк и с гендерной принадлежностью клиента¶

In [22]:
df2 = pd.get_dummies (df, columns=['city', 'gender'], drop_first= False)
In [23]:
df2.head()
Out[23]:
userid score age equity balance products credit_card last_activity est_salary churn city_Ростов city_Рыбинск city_Ярославль gender_Ж gender_М
0 183012 850 25 1 59214.82 2 0 1 75719.14 1 0 1 0 1 0
1 146556 861 37 5 850594.33 3 1 0 86621.77 0 0 1 0 1 0
2 120722 892 30 0 NaN 1 1 1 107683.34 0 0 1 0 1 0
3 225363 866 51 5 1524746.26 2 0 1 174423.53 1 0 0 1 1 0
4 157978 730 34 5 174.00 1 1 0 67353.16 1 0 0 1 0 1
In [24]:
df = df2.copy()

Анализ данных¶

In [25]:
cm = df.corr() 
fig, ax = plt.subplots(figsize = (14, 14))


sns.heatmap(cm, annot = True, square=True, cmap="plasma")

plt.title('Матрица корреляций')
plt.show()

Согласно матрице наибольшая связь с оттоком клиентов наблюдается у следующих параметров:

  • оценка собственности клиента (equity)

  • баланс счёта (balance)

  • количество используемых продуктов банка (products)

Определить зависимость баллов кредитного скоринга и вероятности ухода¶

Для определения зависимости баллов кредитного скоринга (кредитный рейтинг) и вероятности уходя сделаем следующие действия:

  • посчитаем коэфициэнт корреляции Пирсона,

  • построим распределение данных в зависимости от вероятности ухода

  • сравним медианные или средние значения исследуемого параметра в зависимости от вероятности ухода.

In [26]:
df['score'].corr(df['churn']).round(2)
Out[26]:
0.1
In [27]:
fig = px.histogram(df, x="score", color="churn", marginal="rug",
                   hover_data=df.columns, title='Распределение кредитного рейтинга в зависимости от оттока клиентов')
fig.update_layout(xaxis_title='Кредитный рейтинг')
fig.show()
In [28]:
df.pivot_table(index = 'churn', values = 'score', aggfunc = 'median').reset_index()
Out[28]:
churn score
0 0 847
1 1 866

Кредитный скоринг остающихся клиентов более равномерно распределён от 0 до 1000, кредитный скоринг уходящих клиентов начинается от 700 и максимально сосредоточен в области 800-930.

У уходящих клиентов медианный кредитный скоринг 866, у остающихся 847.

Определить зависимость вероятности ухода от города¶

In [29]:
specs = [[{'type':'domain'}, {'type':'domain'}, {'type': 'domain'}]]
colors = ['rgb(33, 75, 99)', 'rgb(79, 129, 102)']
fig = make_subplots(rows=1, cols=3, specs=specs)

# Define pie charts
fig.add_trace(go.Pie(labels=df['churn'], values=df['city_Ярославль'], name='Ярославль',
                     marker_colors=colors, scalegroup='one', title='Ярославль'), 1, 1)
fig.add_trace(go.Pie(labels=df['churn'], values=df['city_Рыбинск'],name='Рыбинск',
                     marker_colors=colors, scalegroup='one', title= 'Рыбинск'), 1, 2)
fig.add_trace(go.Pie(labels=df['churn'], values=df['city_Ростов'], name='Ростов',
                     marker_colors=colors, scalegroup='one', title= 'Ростов'), 1, 3)

fig.update(layout_title_text='Соотношение уходящих и не уходящих клиентов по городам',
           layout_showlegend=True)

fig = go.Figure(fig)
fig.show()

Больше всего клиентов уходит в Ярославле ( как в количественном выражении, так и в процентном) 19,2% - 1094 чел.

Какого пола клиенты чаще уходят¶

In [30]:
df_gender1 =  df.pivot_table(index = 'gender_Ж', values = 'churn', aggfunc = 'sum').reset_index()
df_gender1
Out[30]:
gender_Ж churn
0 0 1161.0
1 1 631.0

Отток мужчин в 2 раза больше, чем женщин.

Посмотрим сколько мужчин и женщин вообще обслуживается в банке

In [31]:
df_gender = df.groupby(['gender_Ж'])[['gender_Ж']].count().rename(columns={'gender_Ж': 'count'}).reset_index()
df_gender
Out[31]:
gender_Ж count
0 0 4878
1 1 4859
In [32]:
specs = [[{'type':'domain'}, {'type':'domain'}]]
colors = ['rgb(129, 180, 179)', 'rgb(124, 103, 37)']
fig = make_subplots(rows=1, cols=2, specs=specs)

fig.add_trace(go.Pie(labels=df_gender1['gender_Ж'], values=df_gender1['churn'], name='Ушедшие клиенты',
                     marker_colors=colors, title='Ушедшие клиенты. 0- мужчины, 1- женщины', textinfo='label+percent'), 1, 1)
fig.add_trace(go.Pie(labels=df_gender['gender_Ж'], values=df_gender['count'],name='0- мужчины, 1- женщины',
                     marker_colors=colors,  title= 'Все клиенты. 0- мужчины, 1- женщины', textinfo='label+percent'), 1, 2)

fig.update(layout_title_text='Зависимость оттока клиентов от пола',
           layout_showlegend=True)

fig = go.Figure(fig)
fig.show()

В общей выборке клиенты по половому признаку делятся практически пополам. В категории ушедших клиентов мужчин почти в 2 раза больше чем женщинЖ (64,8% мужчин и 35,2% женщин)

Определить зависимость вероятности ухода от возраста¶

Для определения зависимости баллов кредитного скоринга (кредитный рейтинг) и вероятности уходя сделаем следующие действия:

  • посчитаем коэфициэнт корреляции Пирсона,

  • построим распределение данных в зависимости от вероятности ухода

  • сравним медианные или средние значения исследуемого параметра в зависимости от вероятности ухода.

In [33]:
df['age'].corr(df['churn']).round(2)
Out[33]:
-0.05
In [34]:
fig = px.histogram(df, x="age", color="churn", marginal="rug",
                   hover_data=df.columns, title='Распределение возраста в зависимости от оттока клиентов')
fig.update_layout(xaxis_title='Возраст')
fig.show()

Распределение не симметрично, поэтому для сравнения "среднего" возраста ушедших и не ушедших клиентов будем использовать медиану

In [35]:
df.pivot_table(index = 'churn', values = 'age', aggfunc = 'median')
Out[35]:
age
churn
0 40
1 39

Сильной разницы в возрасте между отточными и не отточными клиентами не наблюдается. Самый частый возраст оттока с 30 до 40 лет

Определить зависимость вероятности ухода от оценки собственности клиента¶

In [36]:
df['equity'].corr(df['churn']).round(2)
Out[36]:
0.26
In [37]:
df_no_churn = df.loc[df['churn']==0].groupby(['equity'])[['churn']].count()\
.rename(columns={'churn': 'count'}).reset_index().sort_values(by='count', ascending= False)
df_no_churn
Out[37]:
equity count
0 0 2348
4 4 1363
5 5 1320
3 3 1204
2 2 879
1 1 670
6 6 102
7 7 43
8 8 10
9 9 6
In [38]:
df_churn = df.loc[df['churn']==1].groupby(['equity'])[['churn']].count()\
.rename(columns={'churn': 'count'}).reset_index().sort_values(by='count', ascending= False)
df_churn
Out[38]:
equity count
5 5 560
4 4 461
3 3 317
2 2 166
1 1 93
0 0 88
6 6 58
7 7 36
9 9 7
8 8 6
In [39]:
specs = [[{'type':'domain'}, {'type':'domain'}]]
colors = ['rgb(129, 180, 179)', 'rgb(124, 103, 37)']
fig = make_subplots(rows=1, cols=2, specs=specs)

fig.add_trace(go.Pie(labels=df_churn['equity'], values=df_churn['count'], name='Ушедшие клиенты',
                     marker_colors=colors, title='Ушедшие клиенты', textinfo='label+percent'), 1, 1)
fig.add_trace(go.Pie(labels=df_no_churn['equity'], values=df_no_churn['count'],name='Оставшиеся клиенты',
                     marker_colors=colors,  title= 'Оставшиеся', textinfo='label+percent'), 1, 2)

fig.update(layout_title_text='Распледеление клиентов в зависимости от оценки собственности среди ушедших и оставшихся',
           layout_showlegend=True)

fig = go.Figure(fig)
fig.show()

Среди ушедших клиентов наибольшее количество 31% имели оценку собственности 5,на втором месте 26% -4. Среди оставшихся немного иное распределение: наибольшее количество имеют оценку 0 - 30%, на втором месте также оценка 4 - 17,2%

Посмотрим соотношение оставшихся и ушедших клиентов в зависимости от оценки собственности.

In [40]:
df_group=df.groupby(['equity', 'churn'])[['userid']].count().rename(columns={'userid': 'count'}).reset_index()
#добавим проценты
df_group['ratio'] = (df_group['count']/df_group.groupby('equity')['count'].transform('sum')).round(2)*100
df_group
Out[40]:
equity churn count ratio
0 0 0 2348 96.0
1 0 1 88 4.0
2 1 0 670 88.0
3 1 1 93 12.0
4 2 0 879 84.0
5 2 1 166 16.0
6 3 0 1204 79.0
7 3 1 317 21.0
8 4 0 1363 75.0
9 4 1 461 25.0
10 5 0 1320 70.0
11 5 1 560 30.0
12 6 0 102 64.0
13 6 1 58 36.0
14 7 0 43 54.0
15 7 1 36 46.0
16 8 0 10 62.0
17 8 1 6 38.0
18 9 0 6 46.0
19 9 1 7 54.0
In [41]:
fig = px.bar(df_group, x='equity', y='ratio', color = 'churn',\
             title='Зависимость оттока клиентов от оценки собственности клиента')
fig.update_layout(xaxis_title='Оценка собственности',
                   yaxis_title='Доля клиентов, %')
fig.show()

Наибольшая доля ушедших клиенто приходится на оценку собственности - 9. Заметна тенденция увеличения доли ушедших клиентов у увеличением оценки собственности (оценка 8 выбивается из немного из этой тенденции)

На мой взгляд зависимоть от оценки собственности есть. С увеличением оценки увеличивается отток.Об этом говорит последний график, коэфициент Пирсона тоже. Конечно клиентов с оценкой собственности больше 7 не так много, чтобы делать однозначный вывод, но по крайней мере до 7 - тенденция явно просматривается.

Определить зависимость вероятности ухода от баланса на счёте¶

In [42]:
df['balance'].corr(df['churn']).round(2)
Out[42]:
0.2
In [43]:
fig = px.histogram(df, x="balance", color="churn", marginal="rug",
                   hover_data=df.columns, title='Распределение баланса счёта в зависимости от оттока клиентов')
fig.update_layout(xaxis_title='Баланс, руб')
fig.show()
In [44]:
df_balance = df.pivot_table(index = 'churn', values = 'balance', aggfunc = 'median').reset_index()
df_balance
Out[44]:
churn balance
0 0 471966.695
1 1 770771.685
In [45]:
fig = px.bar(df_balance, x='churn', y='balance', \
             title='Зависимость оттока от  баланса')
fig.update_layout(xaxis_title='1: клиент ушёл, 0: клиент остаётся',
                   yaxis_title='Баланс, руб')
fig.show()

В среднем у уходящих клиентов баланс счета выше, чем у остающихся. Наибольшее количество ушедших клиентов имеет баланс от 70 до 900 тыс

Определить зависимость вероятности ухода от количества продуктов, которыми пользуется клиент¶

In [46]:
df['products'].corr(df['churn']).round(2)
Out[46]:
0.3
In [47]:
df_products=df.groupby(['products', 'churn'])[['userid']].count().rename(columns={'userid': 'count'}).reset_index()
#добавим проценты
df_products['ratio'] = (df_products['count']/df_products.groupby('products')['count'].transform('sum')).round(2)*100
df_products
Out[47]:
products churn count ratio
0 1 0 2960 93.0
1 1 1 230 7.0
2 2 0 4061 81.0
3 2 1 964 19.0
4 3 0 731 71.0
5 3 1 294 29.0
6 4 0 172 37.0
7 4 1 295 63.0
8 5 0 21 70.0
9 5 1 9 30.0
In [48]:
fig = px.bar(df_products, x='products', y='ratio', color = 'churn',\
             title='Зависимость оттока клиентов от количества используемых продуктов')
fig.update_layout(xaxis_title='Количество продуктов',
                   yaxis_title='Доля клиентов, %')
fig.show()

Доля уходящих клиентов увеличивается при увеличении количества продуктов от 1 до 4, но если клиент начинает пользоваться 5-ю продутками, то вероятность что он лстанется уведичивается. Наибольшая доля оттока у клиентов пользующихся 4-мя продуктами.

In [49]:
df_products.loc[df_products['churn']==1]
Out[49]:
products churn count ratio
1 1 1 230 7.0
3 2 1 964 19.0
5 3 1 294 29.0
7 4 1 295 63.0
9 5 1 9 30.0
In [50]:
fig = px.bar(df_products.loc[df_products['churn']==1], x='products', y='count', \
             title='Зависимость оттока клиентов от количества используемых продуктов')
fig.update_layout(xaxis_title='Количество продуктов',
                   yaxis_title='Количество ушедших клиентов')
fig.show()

Если смотреть по абсолютному количеству, максимальный отток наблюдается у пользователей с 2-мя продуктами.Но этих пользователей в принципе больше. Думаю, что всё же корректнее смотреть на долю, а не на абсолютное значение.

Определить зависимость вероятности ухода от наличия кредитной карты¶

In [51]:
df.pivot_table(index = 'credit_card', values = 'churn', aggfunc = 'sum').reset_index()
Out[51]:
credit_card churn
0 0 804.0
1 1 988.0

Среди ушедших клиентов чуть больше половины имели кредитные карты.

In [52]:
df_credit = df.groupby(['credit_card'])[['credit_card']].count().rename(columns={'credit_card': 'count'}).reset_index()
df_credit
Out[52]:
credit_card count
0 0 3128
1 1 6609
In [53]:
specs = [[{'type':'domain'}, {'type':'domain'}]]
colors = ['rgb(129, 180, 179)', 'rgb(124, 103, 37)']
fig = make_subplots(rows=1, cols=2, specs=specs)

fig.add_trace(go.Pie(labels=df['credit_card'], values=df['churn'], name='Ушедшие клиенты',
                     marker_colors=colors, title='Ушедшие клиенты', textinfo='label+percent'), 1, 1)
fig.add_trace(go.Pie(labels=df_credit['credit_card'], values=df_credit['count'],name='Все клиенты',
                     marker_colors=colors,  title= 'Все клиенты', textinfo='label+percent'), 1, 2)

fig.update(layout_title_text='Зависимость оттока клиентов от наличия кредитной карты. 1- есть карта, 0- нет карты',
           layout_showlegend=True)

fig = go.Figure(fig)
fig.show()

Доля клиентов с кредитными картами среди ушедших клиентов меньше, чем среди общего количества клиентов на 12 %.

Склоняюсь, что зависимость есть, хоть и небольшая. Отрицательный коэффициент корреляции тоже говорит об обратной связи. Логически если ты платишь кредит, то ты просто не можешь уйти из банка, пока не погасишь его.

Определить зависимость вероятности ухода от активности¶

In [54]:
df['last_activity'].corr(df['churn']).round(2)
Out[54]:
0.17
In [55]:
df_activity = df.pivot_table(index = 'last_activity', values = 'churn', aggfunc = 'sum').reset_index()
df_activity
Out[55]:
last_activity churn
0 0 539.0
1 1 1253.0
In [56]:
fig = px.bar(df_activity, x='last_activity', y='churn',\
             title='Зависимость оттока клиентов от активности')
fig.update_layout(xaxis_title='1: активный клиент, 0: неактивный',
                   yaxis_title='Количество ушедших клиентов, руб')
fig
fig.show()

Как это ни странно, среди ушедших пользователей активных было больше чем не автивных больше чем в 2 раза.

Посмотрим доли доли ушедших пользователей среди общего количества с разбивкой по активности.

In [57]:
df_activity1=df.groupby(['last_activity', 'churn'])[['userid']].count().rename(columns={'userid': 'count'}).reset_index()
#добавим проценты
df_activity1['ratio'] = (df_activity1['count']/df_activity1.groupby('last_activity')['count'].transform('sum')).round(2)*100
df_activity1
Out[57]:
last_activity churn count ratio
0 0 0 4114 88.0
1 0 1 539 12.0
2 1 0 3831 75.0
3 1 1 1253 25.0
In [58]:
fig = px.bar(df_activity1, x='last_activity', y='ratio', color = 'churn',\
             title='Зависимость оттока клиентов от активности пользователей. churn: 0 - оставшиеся, 1- ушедшие клиенты')
fig.update_layout(xaxis_title='Активность клиента: 1-активный, 0 - неактивный',
                   yaxis_title='Доля клиентов, %')
fig.show()

Доля ушедших клиентов среди активных пользователей больше примерно на 20%

Определить зависимость вероятности ухода от заработной платы¶

In [59]:
fig = px.histogram(df, x="est_salary", color="churn", marginal="rug",
                   hover_data=df.columns, title='Распределение предполагаемого дохода в зависимости от оттока клиентов')
fig.update_layout(xaxis_title='Предполагаемый доход, руб')
fig.show()
In [60]:
df_salary = df.pivot_table(index = 'churn', values = 'est_salary', aggfunc = 'median').reset_index()
df_salary
Out[60]:
churn est_salary
0 0 116336.630
1 1 123792.985
In [61]:
fig = px.bar(df_salary, x='churn', y='est_salary',\
             title='Зависимость оттока клиентов от дохода')
fig.update_layout(xaxis_title='Отток: 1-ушёл, 0 - остался',
                   yaxis_title='Медианный доход, руб')
fig
fig.show()

Распределение не симметрично, для сравнения посчитали медианы. Есть небольшое различие. У ушедших клиентов медианный доход немного больше, чем у оставшихся.

Произвести анализ вероятности ухода в зависимости сразу от нескольких критериев¶

Выделим несколько сегментов, в которых как мы думаем высока доля ушедших клиентов и посмотрим какой процентоттока получится при сочетании этих сегментов. Посчитаем 3 варианта:

  1. Мужчины с оценкой собственности 7 и выше и балансом больше 400 тыс

  2. Возраст 30-40, пользуются 4-мя продуктами, кредитный рейтинг больше 800

  3. Зарплата выше 70тыс, активные пользователи, пользуются 4-мя продуктами

In [62]:
df_seg1 = df.loc[(df['gender_М']== 1)  & (df['balance'] > 400000) & (df['equity'] >= 7)]
df_seg1 = df_seg1.groupby(['churn'])[['userid']].count().rename(columns={'userid': 'count'}).reset_index()
df_seg1['ratio'] = (df_seg1['count']/(df_seg1['count'].sum())).round(2)*100
df_seg1
Out[62]:
churn count ratio
0 0 19 40.0
1 1 28 60.0
In [63]:
df_seg2 = df.loc[(df['age'] > 30 ) & (df['age'] < 40) & (df['score'] > 800) &(df['products'] ==4)]
df_seg2 = df_seg2.groupby(['churn'])[['userid']].count().rename(columns={'userid': 'count'}).reset_index()
df_seg2['ratio'] = (df_seg2['count']/(df_seg2['count'].sum())).round(2)*100
df_seg2
Out[63]:
churn count ratio
0 0 69 37.0
1 1 116 63.0
In [64]:
df_seg3 = df.loc[(df['est_salary'] > 70000 ) & (df['last_activity'] == 1) &(df['products'] ==4)]
df_seg3 = df_seg3.groupby(['churn'])[['userid']].count().rename(columns={'userid': 'count'}).reset_index()
df_seg3['ratio'] = (df_seg3['count']/(df_seg3['count'].sum())).round(2)*100
df_seg3
Out[64]:
churn count ratio
0 0 88 33.0
1 1 176 67.0
In [65]:
specs = [[{'type':'domain'}, {'type':'domain'}, {'type': 'domain'}]]
colors = ['rgb(33, 75, 99)', 'rgb(79, 129, 102)']
fig = make_subplots(rows=1, cols=3, specs=specs)

# Define pie charts
fig.add_trace(go.Pie(labels=df_seg1['churn'], values=df_seg1['count'], name='Первый вариант сегментации',
                     marker_colors=colors, title='Вариант 1'), 1, 1)
fig.add_trace(go.Pie(labels=df_seg2['churn'], values=df_seg2['count'], name='Второй вариант сегментации',
                     marker_colors=colors, title='Вариант 2'), 1, 2)
fig.add_trace(go.Pie(labels=df_seg3['churn'], values=df_seg3['count'], name='Третий вариант сегментации',
                     marker_colors=colors, title='Вариант 3'), 1, 3)

fig.update(layout_title_text='Анализ вероятности оттока в зависимости от разных критериев (сегментаций)',
           layout_showlegend=True)

fig = go.Figure(fig)
fig.show()

Наибольший процент оттока в третьем варианте сегментации: зарплата выше 70 тыс, пользующихся 4-мя продуктами и имеющие оценку собственности выше 7.

Я пробовала разные сочетания параметров. В итоге наибольшее влияние оказывает то, что клиент пользуется 4-мя продуктами банка.

Составить портрет клиента, склонного уходить¶

Согласно проведённым исследованиям, чаще уходят из банка:

  1. Мужчины в возрасте 30-40 лет

  2. С зарплатой больше 70 тыс. руб

  3. Пользующиеся 4-мя продуктами

  4. Имеющие высокий кредитный скор от 800

  5. Активный пользователь банка

  6. С высокой оценкой собственности (выше 6)

Одним словом уходят успешные, активные клиенты.

Выводы¶

Наибольшее влияние на отток клиентов оказывают следующие параметры:

  • пользование 4-мя продуктами: доля уходящих клиентов увеличивается при увеличении количества продуктов от 1 до 4, но если клиент начинает пользоваться 5-ю продутками, то вероятность что он уйдёт уменьшится. Наибольшая доля оттока у клиентов пользующихся 4-мя продуктами ~63%.

  • активность: доля ушедших клиентов среди активных пользователей больше примерно на 20%

  • гендерная принадлежность к мужскому полу: ~65% отточных клиентов мужчины

  • кредитный рейтинг выше 800: кредитный скоринг остающихся клиентов более равномерно распределён от 0 до 1000, кредитный скоринг уходящих клиентов начинается от 700 и максимально сосредоточен в области 800-930. У уходящих клиентов медианный кредитный скоринг 866, у остающихся 847

  • оценка собвственности: с увеличением оценки увеличивается отток. Наибольшая доля ушедших клиентов приходится на оценку собственности - 9-54%

  • имеют медианный баланс выше чем неотточные клиенты: медианный баланс отточных клиентов ~770тыс. руб., остающихся ~472 тыс. руб.,

  • имеют медианный доход выше чем неотточные клиенты: медианный доход отточных клиентов ~124тыс. руб., остающихся ~116 тыс. руб.,

Параметры, которые меньше влияют на отток клиентов:

  • Город. По всем 3 городам примерно одинаковый % оттока клиентов (от 16 до 19%%)

  • Наличие кредитной карты: доля клиентов с кредитными картами среди ушедших клиентов меньше, чем среди общего количества клиентов на 12

Рекомендации:

  1. Провести более детальный анализ влияния пользования продуктами банка на отток клиентов. По логике, чем большим количеством продуктов пользуется клиент, тем он более лоялен к банку. У нас получается наоборот.

  2. Сформировать выгодные предложения и продукты, ориентированные на портрет отточных клиентов - успешных, активных людей.

Проверка 3-х гипотез¶

Формулирование гипотез¶

Гипотеза 1. Средние доходы пользователей, которые ушли и остались, различаются

Гипотеза 2. Активность пользователей, которые ушли и остались, различаются

Гипотеза 3. Количество продуктов, которыми пользовались ушедшие и оставшиеся клиенты различаются

Тестирование первой гипотезы¶

H_0: Доход клиентов, которые ушли = доход клиентов, которые остались

H_1: Доход клиентов, которые ушли ≠ доход клиентов, которые остались

alpha = 0.05

In [66]:
fig = px.histogram(df, x="est_salary", color="churn", marginal="rug",
                   hover_data=df.columns, title='Распределение предполагаемого дохода в зависимости от оттока клиентов')
fig.update_layout(xaxis_title='Предполагаемый доход, руб')
fig.show()

Чтобы мы могли применять для расчётов t-критерий Стьюдента необходимо, чтобы наши данные были распределены нормально, чтобы выборки были не зависимы друг от друга и дисперсии рассматриваемых генеральных совокупностей были.

Наши данные соответствуют всем условиям, кроме последнего. В равенстве дисперсии мы не можем быть уверены, и поскольку и по количеству данных выборки сильно отличаются, в расчёте мы укажем параметр equal_var = False

In [67]:
churn_1 =  df[df['churn'] == 1]['est_salary'] 
churn_0 = df[df['churn'] == 0]['est_salary'] 
results = st.ttest_ind(churn_1, churn_0, equal_var = False) # results = вызов метода для проверки гипотезы

alpha = 0.05 

print(results.pvalue) 
if results.pvalue < alpha:
    print('Отвергаем нулевую гипотезу')
else:
    print('Не получилось отвергнуть нулевую гипотезу')
0.0007134767724212125
Отвергаем нулевую гипотезу

Средние доходы оставшихся и ушедших клиентов не равны

Тестирование второй гипотезы¶

H_0: Активность клиентов, которые ушли = активности клиентов, которые остались

H_1: Активность клиентов, которые ушли ≠ активности клиентов, которые остались

alpha = 0.05
In [68]:
fig = px.histogram(df, x="last_activity", color="churn", marginal="rug",
                   hover_data=df.columns, title='Активности в зависимости от оттока клиентов')
fig.update_layout(xaxis_title='Активность: 0- не активный, 1- активный', yaxis_title = "Количество пользователей")
fig.show()

Мы имеем дело с непараметрическими данными. Поэтому будем использовать тест Манна-Уитни

In [69]:
churn_1 =  df[df['churn'] == 1]['last_activity'] 
churn_0 = df[df['churn'] == 0]['last_activity'] 
results = st.mannwhitneyu(churn_1, churn_0)

print('p-значение: ', results.pvalue)

alpha = 0.05

if results.pvalue < alpha:
    print('Отвергаем нулевую гипотезу')
else:
    print(
        'Не получилось отвергнуть нулевую гипотезу'
    ) 
p-значение:  5.590492841187725e-62
Отвергаем нулевую гипотезу

Активность ушедших и оставшихся клиентов не равны

Тестирование третьей гипотезы¶

H_0: Количество продуктов клиентов, которые ушли = количество продуктов клиентов, которые остались

H_1: Количество продуктов клиентов, которые ушли ≠ количество продуктов клиентов, которые остались

alpha = 0.05
In [70]:
fig = px.histogram(df, x="products", color="churn", marginal="rug",
                   hover_data=df.columns, title='Распределение количества продуктов в зависимости от оттока клиентов')
fig.update_layout(xaxis_title='Количество продуктов', yaxis_title = "Количество пользователей")
fig.show()

Мы имеем дело с непараметрическими данными. Поэтому будем использовать тест Манна-Уитни

In [71]:
churn_1 =  df[df['churn'] == 1]['products'] 
churn_0 = df[df['churn'] == 0]['products'] 
results = st.mannwhitneyu(churn_1, churn_0)

print('p-значение: ', results.pvalue)

alpha = 0.05

if results.pvalue < alpha:
    print('Отвергаем нулевую гипотезу: разница статистически значима')
else:
    print(
        'Не получилось отвергнуть нулевую гипотезу, вывод о различии сделать нельзя'
    ) 
p-значение:  1.3389821316567326e-154
Отвергаем нулевую гипотезу: разница статистически значима

Количество продуктов , которыми пользовались ушедшие клиенты отличается от количества продуктов, которыми пользуются оставшиеся клиенты

Выводы¶

Проверка гипотез подтвердила наши более ранние выводы о различном поведении клиентов которые ушли и которые остались

Презентация¶

https://drive.google.com/file/d/10FNCRIT3BGlPj-xcA_O_vxaJVD5GzMlh/view?usp=share_link